<?php

/*************************************************************************************************
 * 参数配置组
 *************************************************************************************************/
// 设置编码格式为utf-8，打开session
header("Content-type: text/html; charset=utf-8");
session_start();

/*************************************************************************************************
 * 简单参数组
 *************************************************************************************************/
/**
 * 获取一个get参数
 * @param string $param 参数名称
 * @param string $default 默认值
 * @param bool $safe 是否进行字符串检测，默认true
 */
function get($param, $default = '', $safe = true){
	$result = $_GET[$param] ?? $default;
	return param($result, $safe);
}

/**
 * 获取一个post参数，参数同上
 */
function post($param, $default = '', $safe = false){
	$result = $_POST[$param] ?? $default;
	return param($result, $safe);
}

/**
 * 获取一个put参数，参数同上。put参数由$GLOBALS['PUT']承载，由controller文件统一填充，所以没有set参数
 */
function put($param, $default = '', $safe = true){
	$result = $GLOBALS['PUT'][$param] ?? $default;
	return param($result, $safe);
}

/*************************************************************************************************
 * 复杂参数组
 *************************************************************************************************/
/**
 * 获取一个session
 * @param string $param session参数名称
 * @return any 该session的值
 */
function getsession($param){
	assert('isset($_SESSION[$param]);  //global->getsession');
	return $_SESSION[$param];
}

/**
 * 设置一个session的值
 * @param string $param session参数名称
 * @param any $value 设置为该session的值
 */
function setsession($param, $value){
	$_SESSION[$param] = $value;
}

/**
 * 获取一个cookie的值，特别提醒：setcookie的当页不要getcookie，取不到值的，会出错
 * @param string $param cookie参数名称
 */
function getcookie($param){
	assert('isset($_COOKIE[$param]); //global->getcookie');
	return $_COOKIE[$param];
}

/**
 * 删除一个cookie
 * @param string $param cookie参数名称
 */
function delcookie($param){
	assert('!empty($param); //global->delcookie');
	if (isset($_COOKIE[$param])){
		setcookie($param, '', time() - 3600);
	}
}

/*************************************************************************************************
 * 路由、文件名组
 *************************************************************************************************/
/**
 * 取本次访问的方法，小写
 */
function getmethod(){
	return strtolower($_SERVER['REQUEST_METHOD']);
}

/**
 * 取本次访问的router: master/controller形式，无php扩展名
 */
function getrouter(){
	$master = get('master');
	$controller = get('controller');
	assert('!empty($master); //global->router');
	assert('!empty($controller); //global->router');
	return "${master}/${controller}";
}

/**
 * 取本次访问的action
 */
function getaction(){
	$action = get('action');
	assert('!empty($action); //global->action');
	return $action;
}

/**
 * 取当前页面的php文件名(无扩展名)
 */
function getphp(){
	$name = substr($_SERVER['PHP_SELF'], strrpos($_SERVER['PHP_SELF'], '/') + 1);
	return substr($name, 0, strrpos($name, '.php'));
}

/**
 * 获取文件的扩展名
 * @param string $filename 需要返回扩展名的文件名
 * @return string 取回的文件扩展名
 */
function getext($filename){
	assert('!empty($filename); //global->getext');
	$pathinfo = pathinfo($filename);
	return strtolower($pathinfo['extension']);
}

/*************************************************************************************************
 * 输出组
 *************************************************************************************************/
/**
 * 输出一个信息到页面
 */
function winfo($content){
	echo $content;
	exit();
}

/**
 * 输出一个错误到页面
 */
function werror($content){
	throw new Exception('错误信息：' . $content . '!');
	exit();
}

/**
 * 写日志
 * $content 内容
 * $type 类型
 */
function wlog($content, $type){
	assert('!empty($content); //global->writelog');
	assert('!empty($type); //global->writelog');
	$log = getlog();
	assert('isset($log) && !is_null($log) && !empty($log); //global->writelog');
	$file = $log . '/' . $type . '_' . date('Ymd') . '.log';
	error_log(date('H:i:s') . ' ' . $content . "\r\n", 3, $file);
}

/*************************************************************************************************
 * 字符串生成组
 *************************************************************************************************/
/**
 * 生成随机纯字母字符串
 * @param int $length 生成字符串的位数，默认4
 */
function genchar($length = 4){
	gencode('char', $length);
}

/**
 * 生成随机纯数字字符串
 * @param int $length 生成字符串的位数，默认4
 */
function gennumber($length = 4){
	gencode('number', $length);
}

/**
 * 生成随机字母数字混合字符串
 * @param int $length 生成字符串的位数，默认8
 */
function genmixed($length = 8){
	gencode('mixed', $length);
}

/**
 * 生成指定
 * @param string $type 生成的类型，'char'为字符串型, 'number'为数字型, 'mixed'为混合型
 * @param number $length： 生成字符串的位数
 * @param number $count 生成卡号的数量
 * @param array $list 传址字符串数组，最终结果保存在这个数组里
 */
function genbatch($type, $length, $count, &$list){
	assert('$type==="char" || $type==="number" || $type==="mixed"; //global->genbatch');
	assert('$length>0; //global->genbatch');
	assert('$count>0; //global->genbatch');
	initarray($list);
	$current = count($list);
	for ($i = 0; $i < $count - $current; $i++){
		$list[] = gencode($type, $length);
	}
	array_unique($list);
	if (count($list) < $count) genbatch($type, $count, $list);
}

/*************************************************************************************************
 * 杂项组
 *************************************************************************************************/
/**
 * 获取操作系统类型，在win上统一为开发版，在linux上统一为发布版。
 */
function os(){
	if (PATH_SEPARATOR == ':') return 'LINUX';
	else return 'WINDOWS';
}

/**
 * 根据object或array组装一个使用特殊符号连接的字符串
 */
function assemble($obj, $char = '|'){
	assert('isset($obj) && !is_null($obj); //global->assemble');
	assert('is_object($obj) || is_array($obj); //global->assemble');
	$result = '';
	foreach ($obj as $v){
		if (!empty($result)){
			$result .= $char;
		}
		$result .= $v;
	}
	return $result;
}

/**
 * 自动载入文件
 */
function autoload($dir, $files){
	assert('!empty($dir); //global->autoload');
	assert('!is_null($files) && is_array($files); //global->autoload');
	foreach ($files as $file){
		include $dir . $file;
	}
}

/**
 * 如果数组还未初始化，则初始化为数组
 * @param array $arr 待检查的数组
 */
function initarray(&$arr){
	if (!isset($arr) || is_null($arr)){
		$arr = [];
	}
}

/**
 * 取时间13位
 */
function time13(){
	return round(microtime(true), 3) * 1000;
}

/*************************************************************************************************
 * Ajax返回部分
 *************************************************************************************************/
/**
 * 返回一条成功
 */
function ok(){
	return '{"return": "OK"}';
}

/**
 * 返回一条失败
 * @param string $content 失败的消息
 */
function error($content){
	assert('!empty($content) && $content !== "OK"; //global->async->error');
	return '{"return":"' . $content . '"}';
}

/**
 * 返回一条带有数据返回值的成功消息
 * @param object $data 被当作data属性的object
 */
function data($result = NULL){
	assert('isset($result) && !is_null($result); //global->async->data');
	assert('is_object($result) || is_array($result); //global->async->data');
	$data = json_decode('{"return":"OK"}');
	$data->data = $result;
	return json_encode($data);
}

/*************************************************************************************************
 * 静态类部分
 *************************************************************************************************/
/**
 * 安全配置（加解密key,iv,valid），注意此类一但确定下来，就尽量不要更改
 */
class pwd {
	public static $key = 'I*3$!5jS6a@-)!#I';
	public static $iv = '*iA9B7%*';
	public static $valid = 'V%7&3(#@2a!@sHV';
}

/**
 * 用于简写html页面代码
 */
class html {
	/**
	 * 输出头部html代码
	 * @param array $arrjs 需要导入的js文件地址数组
	 * @param array $arrcss 需要导入的css文件地址数组
	 * @param string $title 标题
	 * @param string $keywords 网页关键字
	 * @param string $description 网页说明
	 */
	public static function head($arrjs = null, $arrcss = null, $title = '', $keywords = '', $description = ''){
		echo "<!DOCTYPE html>";
		echo "<html lang='zh-CN' ng-app='myApp'>";
		echo "<head>";
		echo "<meta charset='utf-8'>";
		echo "<meta http-equiv='X-UA-Compatible' content='IE=edge'>";
		echo "<meta name='viewport' content='width=device-width, initial-scale=1'>";
		echo "<title>", $title, "</title>";
		echo "<meta name='keywords' content='", $keywords, "' />";
		echo "<meta name='description' content='", $description, "' />";
		html::js($arrjs);
		html::css($arrcss);
		echo '</head><body>';
	}

	/**
	 * 输出尾部html代码
	 */
	public static function tail($arrjs = null){
		html::js($arrjs);
		echo '</body></html>';
	}

	/**
	 * 写出js
	 * @param array $arrjs 需要导入的js文件地址数组
	 */
	private static function js($arrjs){
		if (!is_null($arrjs)){
			foreach ($arrjs as $js){
				echo "<script type='text/javascript' src='", $js, "'></script>";
			}
		}
	}

	/**
	 * 写出css
	 * @param array $arrcss 需要导入的css文件地址数组
	 */
	private static function css($arrcss){
		if (!is_null($arrcss)){
			foreach ($arrcss as $css){
				echo "<link href=", $css, " rel='stylesheet'>";
			}
		}
	}
}

/**
 * 简化form操作时的验证部分
 */
class valid{

	/**
	 * 获取valid字符串
	 * @param string $valid 必填，用户编写的、符号规格的valid字符串
	 * @param string $msg 必填，出错时弹出的信息
	 */
	public static function write($valid, $msg){
		$data = explode('|', $valid);
		$result = '';

		// 无()，时直接返回 type；有()，进行各类计算
		foreach ($data as $key=>$val){
			if (!stripos($val, '(')){
				$result .= valid::type($val);
				continue;
			}
			$set = str::middle($val, '(', ')');
			if (stristr($val, 'length')){
				$set = explode(',', $set);
				$result .= valid::length($set[0], $set[1]);
			}else if (stristr($val, 'number')){
				$set = explode(',', $set);
				$result .= valid::number($set[0], $set[1]);
			}else if (stristr($val, 'equal')){
				$result .= valid::equal($set);
			}else if (stristr($val, 'logic')){
				$result .= valid::logic($set);
			}
		}

		// 连接msg，返回
		$result .= valid::msg($msg);
		return $result;
	}

	/**
	 * 验证出错时的信息
	 * @param string $msg 必填，错误信息
	 */
	private static function msg($msg){
		return "vmsg='" . $msg . "'";
	}

	/**
	 * 验证type类型
	 * @param string $type 必填，type的类型
	 */
	private static function type($type){
		return "vtype='" . $type . "' ";
	}

	/**
	 * 验证length类型
	 * @param int $min 必填，最小长度
	 * @param int $max 最填，最大长度
	 */
	private static function length($min, $max){
		return "vlength='" . $min . ',' . $max . "' ";
	}

	/**
	 * 验证number类型
	 * @param float $min 必填，最小值
	 * @param float $max 必填，最大值
	 */
	private static function number($min, $max){
		return "vnumber='" . $min . ',' . $max . "' ";
	}

	/**
	 * 验证equal类型
	 * @param string $id 必填，需要一致的id号
	 */
	private static function equal($id){
		return "vequal='" . $id . "'";
	}

	/**
	 * 验证logic类型
	 * @param string $url 必填，url地址
	 */
	private static function logic($url){
		return "vlogic='" . $url . "'";
	}
}

/**
 * 用于快速输出通用js脚本，前后台通常继承此类
 */
class js{

	/**
	 * 输出table.form的js代码
	 * @param string $table 必填，影响的表名
	 */
	public static function form($table){
		echo "<script>table.form='" . $table . "';</script>";
	}

	/**
	 * 输出table.list的js代码
	 * @param string $table 必填，影响的表名
	 */
	public static function list($table){
		echo "<script>table.list='" . $table . "';</script>";
	}
}

/**
 * 用于简化string操作的类
 */
class str{
	
	/**
	 * 截断一个字符串，兼容utf-8
	 * @param string $str 必填，字符串源串
	 * @param int $length 必填，从索引0开始字符串的长度
	 * @param string $omission 可不填，如果截断，则显示在字符串之后的省略符，默认'...'
	 */
	public static function truncate($str, $length, $omission='...'){
		if (mb_strlen($str, 'utf-8') > $length){
			return mb_substr($str, 0, $length, 'utf-8') . $omission;
		}
		return $str;
	}
	
	/**
	 * 取两个字符或字符串中间的字符串
	 * @param string $str 必填，字符串源串
	 * @param string $left 必填，左侧字符或字符串
	 * @param unknown $right 必填，右侧字符或字符串
	 */	
	public static function middle($str, $left, $right){
		return substr($str, stripos($str, $left) + 1, stripos($str, $right) - stripos($str, $left) - 1);
	}
	
	/**
	 * 取字符串从索引0至target字符串的内容
	 * @param string $str 必填，字符串源串
	 * @param string $split 必填，分割时的中间字符串
	 */
	public static function left($str, $split){
		return substr($str, 0, stripos($str, $split));
	}
	
	/**
	 * 取字符串从指定target字符串之后的内容
	 * @param string $str 必填，目标源串
	 * @param string $target 必填，分割时的中间字符串
	 */
	public static function right($str, $split){
		return substr($str, stripos($str, $split) + 1, strlen($str) - stripos($str, $split));
	}
	
	/**
	 * 从字符源串中删除目标字符串
	 * @param string $str 必填，字符源串
	 * @param string $target 必填，需要删除的目标字符串
	 */
	public static function remove($str, $target){
		return str_ireplace($target, '', $str);
	}
}

/**
 * 用于简化sql语句操作的类
 */
class sql{
	
	/**
	 * 简便获取select语句的方法
	 * @param string $table 必填，数据表名
	 * @param string $columns 必填，读取哪些列，使用纯字符串即可
	 * @param string $where 可不填，与sql::where使用一样的字符串， 默认空
	 * @param any $order 可不填，order的设置，可以传order数组，例：['sort', ['id desc', 'id asc']]，也可以直接传入order字符串，如id desc，默认空
	 * @param any $limit 可不填，limit的设置，例：[0,10]，也可以直接传入limit字符串，如0,10，默认空
	 */
	public static function select($table, $columns, $where='', $order='', $limit=''){
		list($where, $param) = sql::where($where);
		if (!is_null($order)){
			if (is_array($order) && count($order) == 2){
				$order = sql::order($order[0], $order[1]);
			}else if (is_string($order) && $order != ''){
				$order = ' order by ' . $order;
			}			
		}
		if (!is_null($limit) && count($limit) == 2){
			if (is_array($limit) && count($limit) == 2){
				$limit = sql::limit($limit[0], $limit[1]);
			}else if (is_string($limit) && $limit != ''){
				$limit = ' limit ' . $limit;
			}
		}
		return ['select ' . $columns . ' from ' . $table . $where . $order . $limit, $param];
	}
	
	/**
	 * 简便获取insert语句的方法
	 * @param string $table 必填，数据表名
	 * @param string $add 必填，与sql::add使用一样的字符串
	 * @param string $where 可不填，与sql::where使用一样的字符串，默认空
	 */
	public static function insert($table, $add, $where=''){
		list($add, $ap) = sql::add($add);
		list($where, $wp) = sql::where($where);
		$param = array_merge($ap, $wp);
		return ['insert into ' . $table . $add . $where, $param];
	}
	
	/**
	 * 简便获取update语句的方法
	 * @param string $table 必填，数据表名
	 * @param string $set 必填，与sql::set使用一样格式的字符串
	 * @param string $where 可不填，与sql::where使用一样的字符串，默认空
	 */
	public static function update($table, $set, $where=''){
		list($set, $sp) = sql::set($set);
		list($where, $wp) = sql::where($where);
		$param = array_merge($sp, $wp);
		return ['update ' . $table . $set . $where, $param];
	}
	
	/**
	 * 简便获取delete语句的方法
	 * @param string $table 必填，数据表名
	 * @param string $where 必填，与sql::where使用一样的字符串
	 */
	public static function delete($table, $where){
		list($where, $param) = sql::where($where);
		return ['delete from ' . $table . $where, $param];
	}
	
	/**
	 * 通过字符串获取where字符串和param
	 * @param string $str 必填，原始字符串
	 * @return 将where和param以数组形式返回，0元素为where，1元素为param
	 */
	public static function where($str){
		
		// 初始变量
		$items = sql::items($str);
		$where = '';
		$param = [];
		
		// 连接where与param
		foreach ($items as $item){
			$where .= $where != '' ? ' and ' : '';
			if ($item['operator'] == '%'){
				$where .= $item['column'] . ' like ?';
				$param[] = '%' . $item['value'] . '%';
			}else{
				$where .= $item['column'] . $item['operator'] . '?';
				$param[] = $item['value'];
			}
		}
		
		// 返回
		$where = $where == '' ? '' : ' where ' . $where;
		return [$where, $param];
	}
	
	/**
	 * 通过字符串获取add字符串和param
	 * @param string $str 必填，原始字符串
	 * @return 将add和param以数组形式返回，0元素为add，1元素为param
	 */
	public static function add($str){
		
		// 初始变量
		$items = sql::items($str);
		$add = [];
		$mark = [];
		$param = [];
		
		// 将需要输出的列添加到数组
		foreach ($items as $item){
			$add[] = $item['column'];
			$mark[] = '?';
			$param[] = $item['value'];
		}
		
		// 返回
		$add = $add == [] ? '' : '(' . implode(',', $add) . ') ';
		$add .= $mark == [] ? '' : 'values(' . implode(',', $mark) . ') ';
		return [$add, $param];
	}
	
	/**
	 * 通过字符串获取set字符串和param
	 * @param string $str 必填，原始字符串
	 * @return 将set和param以数组形式返回，0元素为set，1元素为param
	 */
	public static function set($str){
		
		// 初始变量
		$items = sql::items($str);
		$set = [];
		$param = [];
		
		// 将需要输出的列添加到数组
		foreach ($items as $item){
			
			// 特别说明，+,-在set中取回操作符的时候应该是+=和-=，但实际取回的是>=和<=，这是统一前缀导致的
			// 在此需要对这两种操作进行单独计算
			if ($item['operator'] == '>='){
				$set[] = $item['column'] . '=' . $item['column'] . '+?';
			}else if ($item['operator'] == '<='){
				$set[] = $item['column'] . '=' . $item['column'] . '-?';
			}else{
				$set[] = $item['column'] . '=?';
			}
			$param[] = $item['value'];
		}
		
		// 返回
		$set = $set == [] ? '' : ' set ' . implode(',', $set) . ' ';
		return [$set, $param];
	}
	
	/**
	 * 通过字符串获取order字符串
	 * @param string $param 必填，用于获取值的参数名
	 * @param array $options 必填，排序设置数组
	 * @return 设置好的order字符串
	 */
	public static function order($param, $options){
		$value = sql::isget() ? get($param) : post($param);
		foreach ($options as $key=>$option){
			if ($key == $value){
				return ' order by ' . $option;
			}
		}
	}
	
	/**
	 * 获取limit字符串
	 * @param int $start 必填，开始索引
	 * @param int $length 必填，获取多少条数据
	 */
	public static function limit($start, $length){
		return ' limit ' . $start . ',' . $length;
	}
	
	/**
	 * 将单个where子句制作成分隔数组返回
	 * @param string $val 必填，解析出的where子句
	 */
	private static function items($str){
	
		// 取全部item
		$data = explode('|', $str);
		$items = [];
		foreach ($data as $key=>$val){
			if ($val == ''){ continue; }
			$item = sql::item($val);
			$items[] = $item;
		}
	
		// 去除无须留下的item，并返回
		foreach ($items as $key=>$item){
			$value = sql::value($item);
			if (sql::ignore($item, $value)){
				unset($items[$key]);
			}else{
				$items[$key]['value'] = $value;
			}
		}
		return $items;
	}
	
	/**
	 * 获取一个条目的值
	 * @param array $item 必填，条目数组
	 */
	private static function value(&$item){
		if ($item['value'] != '' && !$item['select']){
			return $item['value'];
		}else{
			$value = '';
			if ($item['param'] == ''){
				$value = sql::isget() == true ? get($item['column']) : post($item['column']);
			}else{
				$value = sql::isget() == true ? get($item['param']) : post($item['param']);
			}
			if ($item['select'] && ($value === '' || (is_numeric($value) && $value == 0))){
				return $item['value'];
			}
			if ($item['time']){
				$value = strtotime($value);
			}
			return $value;
		}
	}
	
	/**
	 * 获取是否忽略此条目
	 * @param array $item 必填，条目数组
	 * @param any $value 必填，该条目动态获取的值
	 */
	private static function ignore(&$item, $value){
		if ($item['any'] || $item['specify'] || $item['select']){
			return false;
		}
		if ($item['out'] != '' ){
			if ($value == $item['out']) {
				return true;
			}
		}else{
			if ($value === '' || (is_numeric($value) && $value == 0)){
				return true;
			}
		}
		return false;
	}

	/**
	 * 获取子句的特殊设置项
	 * @param string $val 必填，解析出的任意类型的sql子句
	 */
	private static function item(&$val){
		
		// 初始返回的初值
		$result = [
				'operator'=>sql::operator($val),
				'column'=>$val,
				'any'=>false, 
				'time'=>false, 
				'specify'=>false,
				'select'=>false,
				'out'=>'', 
				'value'=>'',
				'param'=>''];
		
		// 先取回param，并将val赋值为去除param之后的字符串
		if (stristr($val, '(')){
			$result['param'] = str::middle($val, '(', ')');
			$val = str::remove($val, '(' . $result['param'] . ')');
		}
		
		// 再计算item的各个值
		if (stristr($val, '*')){
			$result['any'] = true;
			$result['column'] = str::remove($val, '*');
		}else if (stristr($val, '#')){
			$result['time'] = true;
			$result['column'] = str::remove($val, '#');
		}else if (stristr($val, '^')){
			$result['out'] = str::right($val, '^');
			$result['column'] = str::remove($val, '^' . $result['out']);
		}else if (stristr($val, '@')){
			$result['specify'] = true;
			$result['value'] = str::right($val, '@');
			$result['column'] = str::remove($val, '@' . $result['value']);
		}else if (stristr($val, '$')){
			$result['select'] = true;
			$result['value'] = str::right($val, '$');
			$result['column'] = str::remove($val, '$' . $result['value']);
		}
		return $result;
	}
	
	/**
	 * 获取子句的操作符
	 * @param string $val 必填，解析出的任意类型的sql子句
	 */
	private static function operator(&$val){
		
		// 初始字符串操作符与mysql操作符的转换数组
		$operators = [
				'+'=>'>=', 
				'-'=>'<=', 
				'<'=>'<',
				'>'=>'>',
				'!'=>'!=',
				'%'=>'%'];
		
		// 根据数组计算并返回实际操作符
		foreach($operators as $key=>$opt){
			if (stristr($val, $key)) {
				$val = str_ireplace($key, '', $val);
				return $opt;
			}
		}
		return '=';
	}
	
	/**
	 * 获取是否使用get获取参数，默认为get，当post数据比get数据数量多时，则是post
	 */
	private static function isget(){
		if (strtoupper(getmethod()) == 'GET'){
			return true;
		}else{
			false;
		}
	}
}

/**
 * 用于简化mysql操作
 */
class mysql {
	/**
	 * qeury：查询单条sql
	 * @param array $db：数据库数组
	 * @param any $sqls sql语句，可以是字符串也可以是数组
	 * @param any $params：参数列表，可以是单维数组，也可以是二维数组
	 * @return PDOStatement：成功返回stmt，失败返回null
	 */
	// public static function &query($db, $sqls, $params){
	public static function &query($db, $sqls, $params){
		try{
			$pdo = mysql::getpdo($db);
			assert('!is_null($pdo); //global->mysql->query');

			// 如果sqls是字符串，则将sqls和params都转换为数组，否则，验证其数量是否相等
			if (is_string($sqls)){
				$sqls = [
					$sqls];
				$params = [
					$params];
			}else{
				assert('count($sqls) === count($params); //global->mysql->query');
			}
		
			// 执行数据查询，关闭pdo
			$stmts = [];
			$i = 0;
			foreach ($sqls as $key=>$sql){
				$stmt = $pdo->prepare($sql);
				if (!$stmt->execute($params[$i])){
					werror('global->mysql->query: !$stmt->execute($params[$i])');
				}
				$stmt->setFetchMode(PDO::FETCH_ASSOC);
				$stmts[] = $stmt;
				$i++;
			}

			// 将结果与MongoDB一样做成数组返回
			$result = null;
			if (count($stmts) === 1){
				$result = $stmts[0]->fetchAll();
			}else{
				$result = [];
				$i = 0;
				foreach ($sqls as $key=>$val){
					$result[$key] = $stmts[$i]->fetchAll();
					$i++;
				}
			}
			return $result;
		}
		catch(PDOException $e){
			werror($e->getMessage());
		}
	}
	
	/**
	 * push：执行单条更新、添加、删除类语句
	 * @param $db 数据库数组
	 * @param $sqls sql字符串
	 * @param $params 参数列表
	 * @return boolean 成功返回true，失败返回false
	 */
	public static function write($db, $sqls, $params){
		
		// 如果是多条操作，则使用事物
		if (is_array($sqls)){
			return mysql::transaction($db, $sqls, $params);
		}
		
		// 执行单条数据操作
		try{
			$pdo = mysql::getpdo($db);
			
			$stmt = $pdo->prepare($sqls);
			
			$result = $stmt->execute($params);
			echo 1;
			exit();
			$pdo = null;
			return $result;
		}
		catch(PDOException $e){
			werror($e->getMessage());
		}
	}
	
	/**
	 * pushLastId：执行一条更新、添加、删除语句，并返回最后添加的主键值
	 * @param $db：数据库数组
	 * @param $sql：sql语句
	 * @param $params：参数列表
	 * @param $lastId：注明：传址参数。函数结束后，保存最后添加的主键值
	 * @return boolean：成功返回true，失败返回false
	 */
	public static function writelastid($db, $sql, $params, &$lastId){
		try{
			$pdo = mysql::getpdo($db);
			$stmt = $pdo->prepare($sql);
			$result = $stmt->execute($params);
			$lastId = $pdo->lastInsertId();
			$pdo = null;
			return $result;
		}
		catch(PDOException $e){
			werror($e->getMessage());
		}
	}
	
	/**
	 * transaction：执行一个事务
	 * @param $db：数据库数组
	 * @param $sqls：sql字串数组
	 * @param $params：参数数组
	 * @return boolean：成功返回true，失败返回false
	 */
	private static function transaction($db, $sqls, $params){
		$pdo = mysql::getpdo($db, 0);
		try{
			$pdo->beginTransaction();
			foreach ($sqls as $key=>$sql){
				$stmt = $pdo->prepare($sql);
				if (!$stmt->execute($params[$key])){
					werror('Mysql->transaction, !$stmt->execute($params[$key])');
				}
			}
			$pdo->commit();
			$pdo = null;
			return true;
		}
		catch(PDOException $e){
			$pdo->rollBack();
			werror($e->getMessage());
		}
	}
	
	/**
	 * 创建一个pdo连接对象
	 * @param array $db 数据库连接数组
	 */
	private static function getpdo($db, $autocommit = 1){
		$pdo = new PDO($db['host'], $db['user'], $db['pwd']);
		$pdo->query("set names utf8");
		$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, $autocommit);
		$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
		$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
		return $pdo;
	}
}

/**
 * http工具类，实用工具类，主要用于http访问、http下载等操作
 * 使用curl实现，兼容https协议
 */
class http {

	/**
	 * http(https)访问
	 * @param string $url 访问的url字符串
	 * @param array $data 如果为post访问，则带此数据，默认为null
	 */
	public static function request($url, $data = null){
		assert('!empty($url); //global->http->request');

		// 设置curl
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_URL, $url);
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
		if (!empty($data)){
			curl_setopt($curl, CURLOPT_POST, 1);
			curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
		}
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

		// 取值并关闭curl
		$output = curl_exec($curl);
		curl_close($curl);

		// 返回内容
		return $output;
	}

	/**
	 * http(https)下载
	 * @param string $savedir 文件的保存目录
	 * @param string $filename 文件名
	 * @see 注意，此函数并没有
	 */
	public static function download($url, $savedir, $filename){
		assert('!empty($url); //global->http->download');
		assert('!empty($savedir) && is_dir($savedir); //global->http->download');
		assert('!empty($filename); //global->http->download');

		// 设置curl
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_URL, $url);
		curl_setopt($curl, CURLOPT_HEADER, 0);
		curl_setopt($curl, CURLOPT_NOBODY, 0);
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

		// 取回数据并关闭curl
		$output = curl_exec($curl);
		$info = curl_getinfo($curl);
		curl_close($curl);

		// 获取httpFile的头部消息和实际内容（header为冗余，以后备用）
		$httpFile = array_merge(['body'=>$output], ['header'=>$info]);

		// 打开本地文件句柄，并试图写入文件
		$localFile = fopen($savedir . '/' . $filename, 'w');
		if ($localFile){

			// 如果写入成功，关闭本地文件，并返回true
			if (fwrite($localFile, $httpFile['body'])){
				fclose($localFile);
				return true;
			}
		}

		// 下载失败返回false
		return false;
	}
}

/*************************************************************************************************
 * 动态类
 *************************************************************************************************/
/**
 * 简化upload操作
 */
class upload{
	
	/**
	 * @var string 可以上传的类型，由数组标注，如'jpg,gif,png,zip'
	 */
	private $types;
	
	/**
	 * @var int 最多可一次上传几个文件
	 */
	private $max;
	
	/**
	 * @var int 单个文件的最大上传体积，单位mb，注意在php.ini里也有限制，超出大小会报错，所以，需要上传大文件时，请修改php.ini
	 */
	private $length;
	
	/**
	 * @var array 本对象包含的所有上传文件对象数组
	 */
	private $files;
	
	/**
	 * 构造方法
	 * @param string $types 必填，可以上传的类型，由数组标注，如'jpg,gif,png,zip'
	 * @param int $max 必填，最多可一次上传几个文件，必须小于20
	 * @param int $volume 必填，单个文件的最大上传体积，单位mb，必须小于100。注意在php.ini里也有限制，超出大小会报错，所以，需要上传大文件时，请修改php.ini
	 */
	public function __construct($types, $max, $length){
		assert('!empty($types); //global->upload->construct');
		assert('$max>0 && $max<20; //global->upload->construct');
		assert('$length>=1 && $length<=100; //global->upload->construct');
		$this->types = $types;
		$this->max = $max;
		$this->length = $length;
	}
	
	/**
	 * 对外工作方法
	 */
	public function working(){
		$this->inittypes();
		$this->initfiles();
		if ($this->validate()){
			$this->upload();
		}
	}
	
	/**
	 * 初始化类型
	 */
	private function inittypes(){
		$types = explode(',', $this->types);
		$final = [];
		foreach ($types as $key=>$val){
			switch ($val){
				case 'jpg':
					$final[] = 'image/jpeg';
					$final[] = 'image/pjpeg';
					break;
				case 'gif':
					$final[] = 'image/gif';
					break;
				case 'png':
					$final[] = 'image/png';
					break;
				case 'zip':
					$final[] = 'aplication/zip';
					break;
				case 'mp3':
					$final[] = 'audio/mpeg';
					break;
				case 'txt':
					$final[] = 'text/plain';
					break;
				case 'json':
					$final[] = 'application/json';
					break;
				case 'xls':
					$final[] = 'application/vnd.ms-excel';
					break;
				case 'xlsx':
					$final[] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
					break;
				case 'doc':
					$final[] = 'application/msword';
					break;
				case 'docx':
					$final[] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
					break;
				case 'ppt':
					$final[] = 'application/vnd.ms-powerpoint';
					break;
				case 'pptx':
					$final[] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
					break;
				case 'pdf':
					$final[] = 'application/pdf';
					break;
				default:
					break;
			}
		}
		$this->types = $final;
	}
	
	/**
	 * 取所有files
	 */
	private function initfiles(){
		
	}
	
	/**
	 * 验证文件是否合格
	 */
	private function validate(){
		
	}
	
	/**
	 * 最终的上传方法
	 */
	private function upload(){
		
	}
}

/**
 * 所有Ajax的统一操作类，仅限通用
 */
class unity{

	/**
	 * @var array 数据库连接数组
	 */
	private $db;
	
	/**
	 * 初始化
	 * @param array $db 必填，数据库连接数组
	 */
	public function __construct($db){
		$this->db = $db;
	}
	
	/**
	 * 开始路由导航
	 */
	public function working(){
		switch (post('action')){
			case 'add':
				$this->add();
				break;
			case 'edit':
				$this->edit();
				break;
			case 'uprow':
				$this->uprow();
				break;
			case 'upall':
				$this->upall();
				break;
			case 'delete':
				$this->delete();
				break;
			case 'status':
				$this->status();
				break;
			case 'linkage':
				$this->linkage();
				break;
			default:
				break;
		}
	}
	
	/**
	 * 添加动作处理
	 */
	private function add(){
		list($s, $p) = sql::insert(post('table'), post('column'));
		mysql::write($this->db, $s, $p);
		echo ok();
	}
	
	/**
	 * 编辑动作处理
	 */
	private function edit(){
		list($s, $p) = sql::update(post('table'), post('column'), 'id');
		mysql::write($this->db, $s, $p);
		echo ok();
	}
	
	/**
	 * 更新一行动作处理
	 */
	private function uprow(){
		list($s, $p) = sql::update(post('table'), post('column'), 'id');
		mysql::write($this->db, $s, $p);
		echo ok();
	}
	
	/**
	 * 更新全部动作处理
	 */
	private function upall(){
		$keys = explode(',', post('key'));
		$columns = explode('|', post('column'));
		if (count($keys) == 0 || count($columns) == 0){
			echo error('没有可更新的项');
		}
		$sqls = [];
		$params = [];
		foreach ($keys as $key){
			$s = [];
			$p = [];
			foreach ($columns as $column){
					
				// 取id值，排除非当前行，并将当前行数据添加到s, p
				$id = intval(substr($column, stripos($column, '_') + 1, strlen($column) - stripos($column, '_')));
				if ($id == $key){
					$s[] = str_ireplace('_' . $key, '', $column) . '=?';
					$p[] = post($column );
				}
			}
			$s = 'update ' . post('table') . ' set ' . implode(',', $s) . ' where id=?';
			$p[] = $key;
			$sqls[] = $s;
			$params[] =$p;
		}
		mysql::write($this->db, $sqls, $params);
		echo ok();
	}
	
	/**
	 * 删除一条动作处理
	 */
	private function delete(){
		list($s, $p) = sql::delete(post('table'), 'id');
		mysql::write($this->db, $s, $p);
		echo ok();
	}
	
	/**
	 * 改变状态处理
	 */
	private function status(){
		$s = 'update ' . post('table') . ' set ' . post('column') . '=? where id=?';
		$p = [post('value'), post('id')];
		mysql::write(Db::$main, $s, $p);
		echo ok();
	}
	
	/**
	 * 联动菜单处理
	 */
	private function linkage(){
		$s = 'select * from ' . post('table') . ' where ' . post('pcolumn') . '=? order by ' . post('order');
		$p = [post('pvalue')];
		$d = &mysql::query($this->db, $s, $p);
		$r = ['level'=>post('level'), 'data'=>$d];
		echo data($r);
	}
}

/**
 * restful基础类
 */
class restful {

	/**
	 * @var int 如果get中存在time参数，则此属性会被启用，用来计算此次访问使用的毫秒数，并输出到客户端
	 */
	private $time;

	/**
	 * @var string 访问使用的方法(get, post, put, delete等4种中的其中之一)
	 */
	private $method;

	/**
	 * @var string 本次访问使用的路由
	 */
	private $router;

	/**
	 * @var string 本次访问使用的action
	 */
	private $action;

	/**
	 * @var object 本次访问的输出对象(对象或数组)
	 */
	private $output;

	/**
	 * @var array 过滤器注册数组，三维数组。具有：fullActionName, fileName, className, methodNames(数组)字段
	 */
	private $follows;

	/**
	 * 初始化
	 */
	public function __construct(){

		// 初始化时间参数
		$this->inittime();

		// 获取方法、路由和动作
		$this->method = getmethod();
		$this->router = getrouter();
		$this->action = $this->method . '_' . getaction();

		// 执行操作
		$this->doaction();
	}

	/**
	 * 添加一个过滤器
	 */
	protected function addfollow($fullActionName, $fileName, $className, $methodNames){
		initarray($this->follows);
		$this->follows[] = ['fullActionName'=>$fullActionName, 'fileName'=>$fileName, 'className'=>$className, 'methodNames'=>$methodNames];
	}

	/**
	 * 初始化time，初始time为0，如果存在time参数，则将其设置为当前时间13位
	 */
	private function inittime(){
		$this->time = 0;
		if (!empty(get('time'))){
			$this->time = time13();
		}
	}

	/**
	 * 执行action
	 */
	private function doaction(){
		try{
			include 'controller/' . $this->router . '.php';
			$controller = new controller();
			$action = new ReflectionMethod($controller, $this->action);
			$this->output = json_decode($action->invoke($controller));
		}catch(ReflectionException $e){
			werror('Restful->doAction, ' . $e->getMessage());
		}

		// 如果this-output出错，则不应该往下执行
		if ($this->output->return !== 'OK'){
			winfo(json_encode($this->output));
		}

		// 调用follow
		$this->dofollows();

		// 判断$this->time是否为0，如果不为0，则说明需要输出时间，在$this->output中添加time属性
		if ($this->time > 0){
			$this->output->time = time13() - $this->time;
		}

		// 最终输出
		winfo(json_encode($this->output));
	}

	/**
	 * 执行filter
	 */
	private function dofollows(){
		if (!isset($this->follows) || !is_array($this->follows)){
			return;
		}
		foreach ($this->follows as $follow){
				
			// 判断本follow是否是针对此action的，如果否则continue，如果是则进入try，执行方法
			if ($follow['fullActionName'] !== $this->router . '/' . $this->action) continue;
			try{
				// 载入文件，反射类，并实例化
				include 'follow/' . $follow['fileName'];
				$class = new ReflectionClass($follow['className']);
				$instance = $class->newInstanceWithoutConstructor();

				// 遍历方法名并反射、执行
				foreach ($follow['methodNames'] as $methodName){
					$instance->$methodName($this->output->data);
				}
			}
			catch(ReflectionException $e){
				werror('Restful->doFollows, ' . $e->getMessage());
			}
		}
	}
}

/**
 * 分页基类
 */
abstract class paging{
	
	/**
	 * @var array 数据库连接数组
	 */
	private $db;

	/**
	 * @var string count语句
	 */
	private $sqlcount;
	
	/**
	 * @var string query语句
	 */
	private $sqlquery;
	
	/**
	 * @var array sql的参数数组
	 */
	private $params;
	
	/**
	 * @var int 当前页码
	 */
	protected $current;
	
	/**
	 * @var int 每页显示条目数
	 */
	protected $showitem;
	
	/**
	 * @var int 显示的页面链接数量
	 */
	protected $showlink;
	
	/**
	 * @var int 总共条目数
	 */
	protected $totalitem;
	
	/**
	 * @var int 总共页数
	 */
	protected $totalpage;
	
	/**
	 * @var string 跳转用的url，从基类中自动获取，子类继承使用
	 */
	protected $url;
	
	/**
	 * @var array 取回的实际数据对象
	 */
	protected $data = [];

	/**
	 * 初始化，并且，立即执行
	 * @param array $db 必填，数据库连接字符串数组
	 * @param int $showitem 必填，每页显示的条目数
	 * @param int $showlink 必填，导航位置显示的链接数
	 * @param string $sqlcount 必填，统计使用的sql语句
	 * @param string $sqlquery 必填，查询使用的sql语句
	 * @param array $params 必填，统计和查询使用的参数数组
	 */
	public function __construct($db, $showitem, $showlink, $sqlcount, $sqlquery, $params){
		$this->db = $db;
		$this->showitem = $showitem;
		$this->showlink = $showlink;
		$this->sqlcount = $sqlcount;
		$this->sqlquery = $sqlquery;
		$this->params = $params;
		$this->current = intval(get('page', '1'));	// current必须在initurl前取
		$this->initurl();
		$this->working();
	}

	/**
	 * 最终方案的执行
	 */
	private function working(){

		// 取出总条目数，如果条目数小于1，则赋值页数为1，直接退出
		$stmtcount = &mysql::query($this->db, $this->sqlcount, $this->params);
		$this->totalitem = $stmtcount[0]['count'];
		if ($this->totalitem < 1){
			$this->totalpage = 1;
			return;
		}

		// 计算开始条目数，并使用开始条目将limit语句添加到sqlquery中去
		$start = 0;
		if ($this->current > 1){
			$start = ($this->current - 1) * $this->showitem;
		}
		$limit = $start . ',' . $this->showitem;
		$this->sqlquery .= ' limit ' . $start . ',' . $this->showitem;

		// 执行查询，计算总页数
		$this->data = &mysql::query($this->db, $this->sqlquery, $this->params);
		$this->totalpage = ceil($this->totalitem / $this->showitem);
	}
	
	/**
	 * 获取start与end两个值
	 */
	protected function startend(){
		
		// start与end默认的值，为 显示页数>=总页数
		$start = 1;
		$end = $this->totalpage <= $this->showlink ? $this->totalpage : $this->showlink;
		
		// 定义左右之间的距离、中间分隔数字
		$apart = floor($this->showlink/2);
		$split = $apart + 1;
		
		// 如果总页数大于显示页数，并且，当前页大于中间页
		if ($this->totalpage > $this->showlink && $this->current > $split){
				
			// 如果是最后几页，也就是最大页 - 当前页大于左右的距离数
			if ($apart > $this->totalpage - $this->current){
				$start = $this->totalpage - $this->showlink;
				$end = $this->totalpage;
		
				// 如果不是最后几页，则直接由当前页 - 前后距离页数
			}else{
				$start = $this->current - $apart;
				$end = $this->current + $apart;
			}
		}
		return [$start, $end];
	}
	
	/**
	 * 返回取回的数据数组
	 */
	public function data(){
		return $this->data;
	}

	/**
	 * 初始化url的方法，需要转发地址时则重写
	 */
	protected function initurl(){
		unset($_GET['page']);
		foreach ($_GET as $key=>$val){
			$this->url .= $key . '=' . $val . '&';
		}
		$this->url = getphp() . '.php?' . $this->url . 'page=';
	}
	
	/**
	 * 输出分页条的抽象方法
	 */
	abstract public function paging();
}

/**
 * STD3DES加解密类
 * 例子：
 * $key = 'a25f+_4.fef)(Uf5';
 * $iv = 's#f!3=-s';
 * $msg = '9a5eaf7992935f46137d98c680f7ff17';
 * $des = new STD3Des ( $key, $iv );
 * $rs1 = $des->encrypt ( $msg );
 * echo urlencode($rs1) . '<br />';
 * $rs2 = $des->decrypt ( $rs1 );
 * echo $rs2;
 */
class des {

	/**
	 * @var string 任意位数的key字符串
	 */
	private $key;

	/**
	 * @var string iv只允许8位字符串
	 */
	private $iv;

	/**
	 * 构造，传递二个已经进行base64_encode的KEY与IV
	 * @param string $key
	 * @param string $iv(必须为8位字符串)
	 */
	function __construct($key, $iv){
		assert('!empty($key); //STD3DES->construct');
		assert('!empty($iv); //STD3DES->construct');
		$this->key = base64_encode($key);
		$this->iv = base64_encode($iv);
	}

	/**
	 * 加密
	 * @param string $value 需要加密的字符串
	 * @return 加密后的字符串
	 */
	public function encrypt($value){
		$td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
		$iv = base64_decode($this->iv);
		$value = $this->paddingPKCS7($value);
		$key = base64_decode($this->key);
		mcrypt_generic_init($td, $key, $iv);
		$ret = base64_encode(mcrypt_generic($td, $value));
		mcrypt_generic_deinit($td);
		mcrypt_module_close($td);
		return $ret;
	}

	/**
	 * 解密
	 * @param string $value 需要解密的字符串
	 * @return 解密过的字符串
	 */
	public function decrypt($value){
		$td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
		$iv = base64_decode($this->iv);
		$key = base64_decode($this->key);
		mcrypt_generic_init($td, $key, $iv);
		$ret = trim(mdecrypt_generic($td, base64_decode($value)));
		$ret = $this->unpaddingPKCS7($ret);
		mcrypt_generic_deinit($td);
		mcrypt_module_close($td);
		return $ret;
	}

	/**
	 * 填写padding
	 * @param string $data 要填写padding的数据
	 */
	private function paddingPKCS7($data){
		$block_size = mcrypt_get_block_size('tripledes', 'cbc');
		$padding_char = $block_size - (strlen($data) % $block_size);
		$data .= str_repeat(chr($padding_char), $padding_char);
		return $data;
	}

	/**
	 * 解padding
	 * @param string $text 要解padding的数据
	 */
	private function unpaddingPKCS7($text){
		$pad = ord($text{strlen($text) - 1});
		if ($pad > strlen($text)){return false;}
		if (strspn($text, chr($pad), strlen($text) - $pad) != $pad){return false;}
		return substr($text, 0, -1 * $pad);
	}
}
/*************************************************************************************************
 * 以下函数仅做为全局函数的辅助而存在，请不要调用
 *************************************************************************************************/
/**
 * 获取参数的后半部执行方法，用于过滤危险字符，并检查是否为数字类型，如果是数字类型则返回float类型数据
 * @param string $result 已经通过$_***变量取回的值
 * @param bool $safe 是否过滤危险字符串
 */
function param($result, $safe){
	$result = urldecode($result);
	$result = $safe ? strip_tags($result) : $result;
// 	if (is_numeric($result)) return floatval($result);
// 	else return $result;
	return $result;
}

/**
 * 生成代金券和金卡的卡号
 * @param string $type 生成的类型，'char'为字符串型, 'number'为数字型, 'mixed'为混合型
 * @param number $length： 生成字符串的位数
 * @return string：生成的号码
 */
function gencode($type, $length = 8){
	$char = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$number = '0123456789';
	$mixed = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
	$source = $$type;
	$result = '';
	for ($i = 0; $i < $length; $i++){
		$result .= substr($source, mt_rand(0, strlen($source) - 1), 1);
	}
	return $result;
}
